/*
 * Copyright (c) 2015 Raytheon BBN Technologies Corp
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.poi.xdgf.usermodel.section.geometry;

import java.awt.geom.Point2D;

import org.apache.poi.POIXMLException;
import org.apache.poi.xdgf.geom.SplineRenderer;
import org.apache.poi.xdgf.usermodel.XDGFCell;
import org.apache.poi.xdgf.usermodel.XDGFShape;

import com.graphbuilder.curve.ControlPath;
import com.graphbuilder.curve.ShapeMultiPath;
import com.graphbuilder.curve.ValueVector;
import com.graphbuilder.geom.PointFactory;
import com.microsoft.schemas.office.visio.x2012.main.CellType;
import com.microsoft.schemas.office.visio.x2012.main.RowType;

public class NURBSTo implements GeometryRow {
	
	NURBSTo _master = null;
	
	// The x-coordinate of the last control point of a NURBS.
	Double x = null;
	
	// The y-coordinate of the last control point of a NURBS.
	Double y = null;
	
	// The second to the last knot of the NURBS.
	Double a = null;
	
	// The last weight of the NURBS.
	Double b = null;
	
	// The first knot of the NURBS.
	Double c = null;
	
	// The first weight of the NURBS.
	Double d = null;
	
	// A NURBS formula.
	String e = null;
	
	Boolean deleted = null;
	
	// TODO: support formulas
	
	public NURBSTo(RowType row) {

		if (row.isSetDel()) deleted = row.getDel();
		
		for (CellType cell: row.getCellArray()) {
			String cellName = cell.getN();
			
			if (cellName.equals("X")) {
				x = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("Y")) {
				y = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("A")) {
				a = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("B")) {
				b = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("C")) {
				c = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("D")) {
				d = XDGFCell.parseDoubleValue(cell);
			} else if (cellName.equals("E")) {
				e = cell.getV();
			} else {
				throw new POIXMLException("Invalid cell '" + cellName + "' in NURBS row");
			}
		}
	}
	
	public boolean getDel() {
		if (deleted != null)
			return deleted;
		
		if (_master != null)
			return _master.getDel();
			
		return false;
	}
	
	public Double getX() {
		return x == null ? _master.x : x;
	}
	
	public Double getY() {
		return y == null ? _master.y : y;
	}
	
	public Double getA() {
		return a == null ? _master.a : a;
	}
	
	public Double getB() {
		return b == null ? _master.b : b;
	}
	
	public Double getC() {
		return c == null ? _master.c : c;
	}
	
	public Double getD() {
		return d == null ? _master.d : d;
	}
	
	public String getE() {
		return e == null ? _master.e : e;
	}
	
	@Override
	public void setupMaster(GeometryRow row) {
		_master = (NURBSTo) row;
	}

	@Override
	public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
		if (getDel()) return;
		
		Point2D last = path.getCurrentPoint();
		
		// A NURBS formula: knotLast, degree, xType, yType, x1, y1, knot1, weight1, ..
		String formula = getE().trim();
		if (!formula.startsWith("NURBS(") || !formula.endsWith(")"))
			throw new POIXMLException("Invalid NURBS formula: " + formula);
		
		String [] components = formula.substring(6, formula.length()-1).split(",");
		
		if (components.length < 8)
			throw new POIXMLException("Invalid NURBS formula (not enough arguments)");
		
		if ((components.length - 4) % 4 != 0)
			throw new POIXMLException("Invalid NURBS formula -- need 4 + n*4 arguments, got " + components.length);
		
		double lastControlX = getX();
		double lastControlY = getY();
		double secondToLastKnot = getA();
		double lastWeight = getB();
		double firstKnot = getC();
		double firstWeight = getD();
		
		double lastKnot = Double.parseDouble(components[0].trim());
		int degree = Integer.parseInt(components[1].trim());
		int xType = Integer.parseInt(components[2].trim());
		int yType = Integer.parseInt(components[3].trim());
		
		double xScale = 1;
		double yScale = 1;
		
		if (xType == 0)
			xScale = parent.getWidth();
		if (yType == 0)
			yScale = parent.getHeight();
		
		// setup first knots/weights/control point
		ControlPath controlPath = new ControlPath();
		ValueVector knots = new ValueVector();
		ValueVector weights = new ValueVector();
		
		knots.add(firstKnot);
		weights.add(firstWeight);
		controlPath.addPoint(PointFactory.create(last.getX(), last.getY()));
		
		// iterate get knots/weights
		int sets = (components.length - 4) / 4;
		for (int i = 0; i < sets; i++) {
			double x1 = Double.parseDouble(components[4 + i*4 + 0].trim());
			double y1 = Double.parseDouble(components[4 + i*4 + 1].trim());
			double k = Double.parseDouble(components[4 + i*4 + 2].trim());
			double w = Double.parseDouble(components[4 + i*4 + 3].trim());
			
			controlPath.addPoint(PointFactory.create(x1*xScale, y1*yScale));
			knots.add(k);
			weights.add(w);
		}
		
		// last knots/weights/control point
		knots.add(secondToLastKnot);
		knots.add(lastKnot);
		
		weights.add(lastWeight);
		
		controlPath.addPoint(PointFactory.create(lastControlX, lastControlY));
		
		ShapeMultiPath shape = SplineRenderer.createNurbsSpline(controlPath, knots, weights, degree);
		path.append(shape, true);
	}
}
